package com.pocket.demo.core.initializing;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.pocket.demo.base.utils.BeanUtils;
import com.pocket.demo.business.dao.HotelMapper;
import com.pocket.demo.business.po.Hotel;
import com.pocket.demo.elasticsearch.dao.HotelDocDao;
import com.pocket.demo.elasticsearch.doc.HotelDoc;
import com.pocket.demo.elasticsearch.service.HotelDocService;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 【demo-elasticsearch-3】<br>
 *
 * <p> 项目启动后同步 hotel 表中的数据到 elasticsearch 中 <br>
 *
 * <p>项目启动时 执行一些操作 的方法：
 * 1. 实现接口 CommandLineRunner 重写 run 方法（Spring Boot 提供） <br>
 *      执行时机：springboot完全初始化后，源码 {@link org.springframework.boot.SpringApplication# callRunner(Runner, ApplicationArguments)} <br>
 * 2. 实现接口 ApplicationRunner 重写 run 方法（Spring Boot 提供） <br>
 *      执行时机：springboot完全初始化后，源码 {@link org.springframework.boot.SpringApplication# callRunner(Runner, ApplicationArguments)} <br>
 * 3. 实现接口 ApplicationListener 重写 onApplicationEvent 方法（Spring 提供） <br>
 *      执行时机：IOC容器所有Bean初始化完成时，springIOC容器会发布一个 ContextRefreshedEvent 事件 <br>
 * 4. 非静态void方法 添加注解 @PostConstruct（JDK 提供） <br>
 *      执行时机：Bean初始化的时，Constructor（构造方法）–>@Autowired（依赖注入）–>@PostConstruct（注释的方法） <br>
 * 5. 实现接口 InitializingBeans 实现 afterPropertiesSet 方法（Spring 提供） <br>
 *      执行时机：在 @PostConstruct 后 <br>
 * <p>整体执行顺序：static方法 > @PostConstruct > InitializingBean > ApplicationRunner > CommandLineRunner <br>
 *
 * @author cookie.
 **/
@Order(3)
@Component
public class SyncHotelToElastic implements PocketInitialize {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncHotelToElastic.class);

    @Resource private HotelDocService hotelDocService;
    @Resource private HotelDocDao hotelDocDao;

    @Resource private HotelMapper hotelMapper;
    @Override
    public void initialize(ApplicationReadyEvent event){
        try{
            initIndex();
            if (hotelDocDao.count() == 0L){
                syncHotelToElastic();
            }
        } catch (Exception e){
            LOGGER.error("Pocket init task syncHotelToElastic error : {}", e, e);
        }
    }

    /**
     * 初始化索引
     */
    private void initIndex(){
        boolean indexExist = hotelDocService.isIndexExist();
        if (indexExist){
            LOGGER.debug("Elasticsearch [HOTEL] index exist, skip init index");
        } else {
            LOGGER.debug("Elasticsearch [HOTEL] index not exist, start init index");
            hotelDocService.createIndex(true);
        }
    }

    /**
     * 同步 hotel数据 到 Elasticsearch
     */
    private void syncHotelToElastic(){
        AtomicLong count = new AtomicLong(0L);
        List<HotelDoc> hotelDocList = new ArrayList<>();
        hotelMapper.selectList(Wrappers.emptyWrapper(), resultContext -> {
            Hotel hotel = resultContext.getResultObject();
            HotelDoc hotelDoc = BeanUtils.toBean(hotel, HotelDoc.class);
            hotelDoc.setLocation(hotel.getLatitude() + "," + hotel.getLongitude());
            hotelDocList.add(hotelDoc);

            if (count.get() % 100 == 0){
                hotelDocDao.saveAll(hotelDocList);
                hotelDocList.clear();
            }
            count.getAndIncrement();
        });
        if (hotelDocList.size() > 0){
            hotelDocDao.saveAll(hotelDocList);
        }
        LOGGER.debug("Sync hotel data to elasticsearch, total count : {}", count.get());
    }
}
